#define _XOPEN_SOURCE 

#include "tcp.h"
#include "tools.h"
#include "md5.h"

#define READ_DATA_SIZE	1024
#define MD5_SIZE		16
#define MD5_STR_LEN		(MD5_SIZE * 2)

void client(char* ip, uint16_t port);
int Compute_file_md5(const char *file_path, char *value);
int download_file(int peerfd, char* filename);
void upload_file(int peerfd, char* filename);

int main(int argc, char *argv[])
{
    //./client ip port
    ARGS_CHECK(argc,3);
    
    printf("\033[;47;32m你好，欢迎使用！\033[0m\n");
    client(argv[1],atoi(argv[2]));

    return 0;
}

void client(char* ip,uint16_t port)
{
	//1. 创建套接字
	int clientfd =  socket(AF_INET, SOCK_STREAM, 0);
	ERROR_CHECK(clientfd, -1, "socket");

	struct sockaddr_in serveraddr;
	memset(&serveraddr, 0, sizeof(serveraddr));
	serveraddr.sin_family = AF_INET;
	serveraddr.sin_port = htons(port);
	serveraddr.sin_addr.s_addr = inet_addr(ip);

	//2. 向服务器发起建立连接的请求
	int ret;
    ret = connect(clientfd, 
			(struct sockaddr *)&serveraddr, 
			sizeof(serveraddr));
	ERROR_CHECK(ret, -1, "connect");
	printf("Connect has completed.\n");
    
    fd_set readfds;
    
    train_t train;
    int length=0;
    int dataType=0;
    
    char buff[BUFFSIZE];
    
    char salt[20];
    char passwd[100];
    char cryptpasswd[100];
    char filename[50];
    
    while(1){
        FD_ZERO(&readfds);
        FD_SET(STDIN_FILENO, &readfds);
        FD_SET(clientfd, &readfds);
        
        select(clientfd+1, &readfds, NULL, NULL, NULL);
            
        if(FD_ISSET(clientfd, &readfds)){
            
            // 接收数据长度和数据类型
            recv(clientfd, &length, 4, MSG_WAITALL);
            recv(clientfd, &dataType, 4, MSG_WAITALL);
	        
            printf("\033[;47;32mdataLen:%5d, dataType:%2d\033[0m\n", length, dataType);
            
            if(dataType == 0){          //默认
	            memset(buff, '\0', sizeof(buff));
	            ret = recv(clientfd, buff, length, MSG_WAITALL);
	            printf("%s\n",buff);

            }else if(dataType == 1){          //salt
	            memset(salt, '\0', sizeof(salt));
	            ret = recv(clientfd, salt, length, MSG_WAITALL);
	            //printf("salt: %s\n",salt);
                printf(">> 请输入密码:\n");

            }else if(dataType == 2){        // 下载文件
                // 接收文件的名字
                memset(filename, '\0', sizeof(filename));
                ret = recv(clientfd, filename, length, MSG_WAITALL);
	            printf("> filename:%s\n", filename);
                
                download_file(clientfd, filename);            
                
                dataType = 0;

            }else if(dataType == 3){         // 上传文件
                // 接收数据内容
	            memset(buff, '\0', sizeof(buff));
	            ret = recv(clientfd, buff, length, MSG_WAITALL);
                
                if(strcmp(buff, "ack") == 0){
                    printf("%s\n", buff);
                    
                    // 开始上传文件
                    upload_file(clientfd, filename);            

                }else if(strcmp(buff, "skip") == 0){
                    printf("%s\n", buff);
                    
                    // 什么都不用做
                
                }else{
                    memset(filename, '\0', sizeof(filename));
	                strncpy(filename, buff, length);
   
	                // 生成md5
                    char file_path[50];
	                
                    memset(file_path, '\0', sizeof(file_path));
                    strcat(file_path, "../clientware/");
                    strcat(file_path, filename);
    
                    char md5_str[MD5_STR_LEN + 1];
	                ret = Compute_file_md5(file_path, md5_str);
                    
                    if (0 == ret)
	                {
                        printf("%s md5: %s\n", filename, md5_str);
                        
                        //发文件的md5给对端
                        memset(train.data, '\0', sizeof(train.data));
                        strcpy(train.data, md5_str);
                        train.length = strlen(train.data);
                        train.dataType = 3;
                        ret = sendn(clientfd, &train, 8 + train.length);
	                }

                }
                
                dataType = 0;
            }
            
            if(ret == 0 ){
                break;
            }
        }
            
        if(FD_ISSET(STDIN_FILENO, &readfds)){
            memset(buff, '\0', sizeof(buff));
            read(STDIN_FILENO, buff, sizeof(buff));
            
            //printf("%s\n", buff);

            if(strcmp(buff, "\n") != 0){
                if(dataType == 0){
                    //printf("send command\n");    
                    ret = send(clientfd, buff, strlen(buff), 0);
                
                }else if(dataType == 1){
	                memset(passwd, '\0', sizeof(cryptpasswd));
                    strncpy(passwd, buff, strlen(buff));
	                passwd[strlen(passwd)-1] = '\0';
                    //printf("passwd: %s\n",passwd);

	                memset(cryptpasswd, '\0', sizeof(cryptpasswd));
                    strcpy(cryptpasswd, crypt(passwd, salt));
                    send(clientfd, cryptpasswd, strlen(cryptpasswd), 0);

                }else if(dataType == 2 || dataType == 3){
                    //不响应键盘
                    ;
                
                }
                
                printf("\n");
            }
            
        }
    }
    
	close(clientfd);
}


// 客户端下载文件
int download_file(int peerfd, char* filename){
	int length;
    int dataType;
	int ret;

    //应用层接收缓冲区
	char recvBuff[NETBUFSIZE];

	// 在服务端本地创建文件
	char file_path[100];
    
    memset(file_path, '\0', sizeof(file_path));
    strcat(file_path, "../clientware/");
    strcat(file_path, filename);
    printf("> file_path: %s\n", file_path);

    // 打开文件
    int fileFd = open(file_path, O_CREAT|O_RDWR, 0644);

	// 接收文件的大小
	off_t fileSize;
	
    ret = recv(peerfd, &length, 4, MSG_WAITALL);
	printf("> length: %d\n", length);
	ret = recv(peerfd, &dataType, 4, MSG_WAITALL);
	printf("> dataType:%d\n", dataType);
    ret = recv(peerfd, &fileSize, sizeof(fileSize), MSG_WAITALL);
	printf("> fileSize: %ld\n", fileSize);
	
    off_t slice = fileSize / 100;

	// 接收文件的内容
	off_t downloadSize = 0;
	off_t lastSize = 0;
    
    char bar[52];
    memset(bar,'\0',sizeof(bar));
    
    const char *lable="|/-\\";
	
    int i = 0, cnt = 0;
    
    while(downloadSize < fileSize) {
		ret = recv(peerfd, &length, 4, MSG_WAITALL);
		ret = recv(peerfd, &dataType, 4, MSG_WAITALL);
		
        if(0 == ret) {
			printf("> %s has recv all data\n", filename);
			break;
		}
        
		memset(recvBuff, '\0', sizeof(recvBuff));
		ret = recv(peerfd, recvBuff, length, MSG_WAITALL);
		//printf("recv ret=%d,%d\n",ret,length);
		
        downloadSize += ret;

		if(downloadSize - lastSize >slice ||lastSize==0 ||downloadSize==fileSize) {
			//printf(">> download percent:%5.2lf%%\n", (double)downloadSize / fileSize * 100);
            //打印进度条
            if(cnt % 2 == 0){
                bar[i] = '#';
                i++;
            }
            printf("[%-51s][%d%%] %c\r", bar, cnt, lable[cnt%4]);
            fflush(stdout);
            cnt++;
			lastSize = downloadSize;
        }

		// 写入本地
        ret = write(fileFd, recvBuff, ret);
		//printf("write ret=%d,%d\n",ret,length);
		ERROR_CHECK(ret, -1, "write");
	}


    ret = chown(file_path, 1000, 1000);
    ret = chmod(file_path, 0755);

    printf("\n");
	printf("> Download file completed!\n");

    return fileSize;
}


// 客户端上传文件
void upload_file(int peerfd, char* filename){
    train_t train;
   
    int ret;
    
    // 发文件名给对端
    memset(train.data, '\0', sizeof(train.data));
    strcpy(train.data, filename);
    train.length = strlen(filename);
    train.dataType = 3;
    ret = sendn(peerfd, &train, 8 + train.length);

    // 读取client本地的文件
    char file_path[50];
	                
    memset(file_path, '\0', sizeof(file_path));
    strcat(file_path, "../clientware/");
    strcat(file_path, filename);
    printf("> file_path: %s\n", file_path);

    // 打开文件
    int fileFd = open(file_path, O_RDWR);

    // 发送文件的长度
    struct stat st;
    fstat(fileFd, &st);
    printf("> fileSize:%ld\n", st.st_size);
    
    memset(train.data, '\0', sizeof(train.data));
    train.length = sizeof(off_t);
    strncpy(train.data, (char*)&st.st_size, sizeof(off_t));
    train.dataType = 3;
    ret = sendn(peerfd, &train, 8 + train.length);

    // 发送文件内容
    off_t total = 0;
	while(total < st.st_size) {
		memset(train.data, '\0', sizeof(train.data));
		
        ret = read(fileFd, train.data, NETBUFSIZE);
		
        if(0 == ret) {
			printf("> %s send over!\n", filename);
			break;
		}
		
        ERROR_CHECK(ret, -1, "read");
		
        train.length = ret;
        train.dataType = 3;
		ret = sendn(peerfd, &train, 8 + train.length);
		
        total += train.length;
		
	}
	
    printf("> %s send over!\n", filename); 
    
    close(fileFd);

    return;
}


// 计算 md5
int Compute_file_md5(const char *file_path, char *md5_str)
{
	int i;
	int fd;
	int ret;
	unsigned char data[READ_DATA_SIZE];
	unsigned char md5_value[MD5_SIZE];
	MD5_CTX md5;

    printf("file_path: %s\n", file_path);
    
	fd = open(file_path, O_RDONLY);
	if (-1 == fd)
	{
		perror("open");
		return -1;
	}

	// init md5
	MD5Init(&md5);

	while (1)
	{
		ret = read(fd, data, READ_DATA_SIZE);
		if (-1 == ret)
		{
			perror("read");
			return -1;
		}

		MD5Update(&md5, data, ret);

		if (0 == ret || ret < READ_DATA_SIZE)
		{
			break;
		}
	}

	close(fd);

	MD5Final(&md5, md5_value);

	for(i = 0; i < MD5_SIZE; i++)
	{
		snprintf(md5_str + i*2, 2+1, "%02x", md5_value[i]);
	}
	md5_str[MD5_STR_LEN] = '\0'; // add end

	return 0;
}
